package hw2.cs236369.parsing;

import hw2.cs236369.interfaces.IAuthor;
import hw2.cs236369.interfaces.IPaper;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * DBLP SAX Handler
 * 
 * @author gil
 * 
 */
public class DBLPHandler extends DefaultHandler {
	/**
	 * Creates a new DBLP Handler with a given regular expression to match
	 * authors with.
	 * 
	 * @param regex Regular Expression to match authors.
	 */
	public DBLPHandler(String regex) {
		//initialize variables
		_regex = regex;
		_map = new HashMap<IAuthor, Collection<IPaper>>();
		_orderedAuthors = new LinkedList<IAuthor>();
		_insideArticle = false;
		_articleType = null;
		_articleYear = 0;
		_articleAuthors = new ArrayList<IAuthor>();
		_currentInfo = INFO_TYPE.UNDEFINED;
		_reading = false;
		_buffer = "";

	}

	@Override
	public void endElement(String ns, String localName, String qName) throws SAXException {
		//if we were processing characters prior the endElement, save the result buffer to the right place
		if (_reading == true) {
			switch (_currentInfo) {
			case AUTHOR_NAME:
				//the result buffer contains an article's author
				_articleAuthors.add(new Author(_buffer));
				break;
			case PUBLICATION_YEAR:
				//the result buffer contains the first specified article's publication year
				if (_articleYear == 0) {
					_articleYear = Integer.parseInt(_buffer);
				}
				break;
			}
			//update reading flag
			_reading = false;
		}
		if (!_insideArticle || qName != _articleType) {
			//we've not reached the end tag of the current article (or we're not inside an article)
			return;
		}
		//we're inside an article's block and we've reached its end

		if (_articleYear == 0 || _articleAuthors == null) { // || _articleName == null) {
			//for some reason there are missing parameters
			//reset the variables
			_articleAuthors = new ArrayList<IAuthor>();
			_articleYear = 0;
			_insideArticle = false;
			return;
			//throw new SAXException("ERROR:: Missing year/author for processed article.");
		}

		//create the processed article as a new paper
		Paper p = new Paper(_articleYear, _articleAuthors);

		//add the newly created paper to each of its authors
		for (IAuthor author : _articleAuthors) {
			if (!author.getName().matches(_regex)) {
				//if current author doesn't match the regex, ignore it.
				continue;
			}
			Collection<IPaper> papers = _map.get(author);
			if (papers == null) {
				//current author doesn't exist, create a new papers list for him
				papers = new ArrayList<IPaper>(1);
				//add the new author to the authors list 
				_orderedAuthors.add(author);
			}
			papers.add(p);
			_map.put(author, papers);
		}
		//reset the variables
		_articleAuthors = new ArrayList<IAuthor>();
		_articleYear = 0;
		_insideArticle = false;
		//_articleName = null;

	}

	@Override
	public void characters(char[] ch, int start, int len) throws SAXException {
		if (_currentInfo == INFO_TYPE.UNDEFINED) {
			//if current characters doesn't belong to any known context, do nothing
			return;
		}
		//create the string from the characters buffer
		String s = new String(String.copyValueOf(ch, start, len));
		//check if its a continuation of the previous buffer. concatenate if true.
		if (_reading) {
			_buffer += s;
		} else {
			_buffer = s;
		}
		_reading = true;

	}

	/**
	 * Gets all authors that matched the regular expression. If no author
	 * matched or parsing did not run, returns null.
	 * 
	 * @return Collection of IAuthors
	 */
	public Collection<IAuthor> getAuthors() {
		if (_orderedAuthors == null) {
			return new LinkedList<IAuthor>();
		}
		return _orderedAuthors;
	}

	/**
	 * Gets all papers for a given author. If no author matched or parsing did
	 * not run, returns null.
	 * 
	 * @param author The given author
	 * @return A collection of the author's IPapers
	 */
	public Collection<IPaper> getAuthorsPapers(IAuthor author) {
		if (_map == null || _map.containsKey(author) == false) {
			return new LinkedList<IPaper>();
		}
		return _map.get(author);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		String result = "";
		for (IAuthor author : _map.keySet()) {
			result += author.getName() + "'s Articles:" + "\n";
			int count = 1;
			for (IPaper paper : _map.get(author)) {
				result += "    " + count + ". Year=" + paper.getPublicationYear() + ", by: \n";
				for (IAuthor a : paper.getAuthors()) {
					result += "        " + a.getName() + "\n";
				}
				count++;
			}
		}
		return result;
	}

	@Override
	public void startElement(String ns, String localName, String qName, Attributes attrs) throws SAXException {
		//new element, set reading indicator to false
		_reading = false;
		if (PAPER_TYPES.contains(qName) && _insideArticle) {
			//we're inside an article and found another article. should not happen...
			throw new SAXException("ERROR:: Found Article wihtin an article!");
		}
		if (PAPER_TYPES.contains(qName)) {
			//if the current element is a known article, update indicators
			_articleType = qName;
			_insideArticle = true;
			return;
		} else if (_insideArticle) {
			//if we're inside an article, check if current element needs special treatment
			switch (qName) {
			case "author":
				_currentInfo = INFO_TYPE.AUTHOR_NAME;
				break;
			case "year":
				_currentInfo = INFO_TYPE.PUBLICATION_YEAR;
				break;
			default:
				_currentInfo = INFO_TYPE.UNDEFINED;
			}
		} else {
			//not an article, ignore.
		}
	}

	/*
	 * Private fields
	 */
	private enum INFO_TYPE {
		UNDEFINED, AUTHOR_NAME, PUBLICATION_YEAR
	};

	final private Collection<String>			PAPER_TYPES	= new ArrayList<String>(Arrays.asList("article", "inproceedings", "proceedings", "book",
																	"incollection", "phdthesis", "mastersthesis", "www"));
	final private String						_regex;
	private Map<IAuthor, Collection<IPaper>>	_map;
	private LinkedList<IAuthor>					_orderedAuthors;
	private boolean								_insideArticle;
	private INFO_TYPE							_currentInfo;
	private String								_articleType;
	private int									_articleYear;
	private Collection<IAuthor>					_articleAuthors;
	private boolean								_reading;
	private String								_buffer;

}
